package com.liuqi.voicenotes.domain

import android.content.Context
import com.liuqi.voicenotes.data.Priority
import com.liuqi.voicenotes.data.Task
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.json.JSONObject
import javax.net.ssl.HttpsURLConnection

object Parser {
    // 兼容旧调用：无 Context 的同步解析，直接走启发式（用于测试等场景）
    fun parseSpeechToTask(raw: String, nextId: Long): Task? = heuristicParse(raw, nextId)

    // 入口：LLM 优先解析，失败或无 key 时回退到启发式
    suspend fun parseSpeechToTask(context: Context, raw: String, nextId: Long): Task? = withContext(Dispatchers.IO) {
        val trimmed = raw.trim()
        if (trimmed.isEmpty()) return@withContext null

        val sp = context.getSharedPreferences("voicenotes_prefs", Context.MODE_PRIVATE)
        val apiKey = (sp.getString("deepseek_api_key", "sk-d61c709c7afc40eda43afc076afd3550") ?: "sk-d61c709c7afc40eda43afc076afd3550").trim()
        val apiBase = (sp.getString("deepseek_api_base", "https://api.deepseek.com") ?: "https://api.deepseek.com").trim()

        if (apiKey.isNotBlank()) {
            runCatching { callDeepSeek(apiBase, apiKey, trimmed) }
                .onSuccess { resp ->
                    parseDeepSeekJson(resp)?.let { parsed ->
                        // 如果 LLM 没有给出 dueAtMillis，尝试基于原始文本推断一个最近的时间点
                        val finalTask = if (parsed.dueAtMillis == null) {
                            inferDueMillisFromText(trimmed)?.let { inferred -> parsed.copy(dueAtMillis = inferred) } ?: parsed
                        } else parsed
                        return@withContext finalTask
                    }
                }
                .onFailure {
                    // 网络或解析失败则回退
                    println("DeepSeek 调用失败: ${it.message}")
                    it.printStackTrace()
                }
        }
        // 回退到本地启发式解析（中文为主）
        return@withContext heuristicParse(trimmed, nextId)
    }

    private fun callDeepSeek(apiBase: String, apiKey: String, input: String): String {
        val url = java.net.URL("${apiBase.removeSuffix("/")}/chat/completions")
        val conn = (url.openConnection() as HttpsURLConnection).apply {
            requestMethod = "POST"
            doOutput = true
            setRequestProperty("Authorization", "Bearer $apiKey")
            setRequestProperty("Content-Type", "application/json")
            setRequestProperty("Accept", "application/json")
            connectTimeout = 15000
            readTimeout = 20000
        }

        val systemPrompt = buildSystemPrompt()
        val userPrompt = buildUserPrompt(input)
        val payload = JSONObject().apply {
            put("model", "deepseek-chat")
            put("temperature", 0.2)
            put("response_format", JSONObject().apply { put("type", "json_object") })
            put("messages", org.json.JSONArray().apply {
                put(JSONObject().apply {
                    put("role", "system")
                    put("content", systemPrompt)
                })
                put(JSONObject().apply {
                    put("role", "user")
                    put("content", userPrompt)
                })
            })
        }.toString()

        // 调试日志：打印发送给大模型的内容
        println("=== DeepSeek 请求内容 ===")
        println("URL: ${url}")
        println("System Prompt: ${systemPrompt}")
        println("User Prompt: ${userPrompt}")
        println("完整 Payload: ${payload}")
        println("========================")

        conn.outputStream.use { os ->
            os.write(payload.toByteArray(Charsets.UTF_8))
        }

        val code = conn.responseCode
        val stream = if (code in 200..299) conn.inputStream else conn.errorStream
        val resp = stream.bufferedReader(Charsets.UTF_8).use(java.io.BufferedReader::readText)
        conn.disconnect()
        
        // 调试日志：打印大模型返回的内容
        println("=== DeepSeek 响应内容 ===")
        println("HTTP Status: ${code}")
        println("Response: ${resp}")
        println("========================")
        
        if (code !in 200..299) throw RuntimeException("DeepSeek error $code: $resp")
        return resp
    }

    private fun parseDeepSeekJson(resp: String): Task? {
        val root = JSONObject(resp)
        val choices = root.optJSONArray("choices") ?: return null
        if (choices.length() == 0) return null
        val content = choices.getJSONObject(0)
            .getJSONObject("message")
            .optString("content")
        if (content.isBlank()) return null
        val obj = runCatching { JSONObject(content) }.getOrNull() ?: return null

        val title = obj.optString("title").ifBlank { null } ?: return null
        val dueMillis = if (obj.has("dueAtMillis")) obj.optLong("dueAtMillis", 0L).takeIf { it > 0L } else null
        val priority = obj.optString("priority").lowercase().let {
            when (it) {
                "low" -> Priority.LOW
                "medium" -> Priority.MEDIUM
                "high" -> Priority.HIGH
                else -> Priority.MEDIUM
            }
        }
        return Task(
            id = 0L,
            title = title,
            dueAtMillis = dueMillis,
            priority = priority
        )
    }

    private fun buildSystemPrompt(): String = """
你是语音待办解析助手。严格输出 JSON，对字段进行如下约束：
{
  "title": 字符串，必须简洁且不超过 40 字，去掉客套话与无关短语；
  "dueAtMillis": 整数或 null，毫秒时间戳；当表达为相对时间时，基于设备当前时间与时区计算；若没有明确时间则为 null；
  "priority": 枚举，low/medium/high，默认 medium；若出现“紧急”“马上”等则提升为 high。
}
只输出一个 JSON 对象，不要包含任何解释性文字或 Markdown。
""".trim()

    private fun buildUserPrompt(input: String): String {
        val tz = java.util.TimeZone.getDefault()
        val formatter = java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss", java.util.Locale.getDefault())
        formatter.timeZone = tz
        val nowStr = formatter.format(java.util.Date(System.currentTimeMillis()))
        val tzId = tz.id
        val rules = "若只说时间而未给出日期，默认指最近的该时间点：如果该时间尚未到达，则视为今天；若已过去，则视为明天。"
        return """
用户语音文本：
$input

当前设备时间：$nowStr；时区：$tzId。
$rules
语义说明：
- 将“提醒我”“帮我记一下”等前缀去掉，保留核心事项。
- 时间解析示例：
  - “今天下午三点” → 今日 15:00（本地时区）；
  - “明天早上八点” → 明日 08:00；
  - “下周一上午十点” → 下周一 10:00；
  - “两小时后” → 当前时间 + 2 小时；
- 若无明确时间，dueAtMillis 为 null。
- 优先级：包含“紧急”“尽快”“立刻”“马上” → high；“顺便”“不急” → low；其他 → medium。
只返回 JSON，不要解释。
""".trim()
    }

    // 本地启发式解析（原有策略的增强版），供回退使用
    private fun heuristicParse(raw: String, nextId: Long): Task? {
        val text = raw.trim()
        if (text.isEmpty()) return null

        // 粗略去除常见前缀
        var title = text
            .replace(Regex("^(请|帮我|麻烦|提醒我|记一下|我需要)"), "")
            .replace(Regex("(一下|一下子|一下儿)$"), "")
            .trim()
        if (title.isBlank()) title = text

        // 简易优先级
        val priority = when {
            Regex("(紧急|立刻|马上|尽快)").containsMatchIn(text) -> Priority.HIGH
            Regex("(不急|随便|有空|顺便)").containsMatchIn(text) -> Priority.LOW
            else -> Priority.MEDIUM
        }

        // 中文时间表达推断：如“下午5点”“三点半”等，默认最近一次出现的该时间（已过则次日）
        val dueAtMillis = inferDueMillisFromText(text)

        return Task(
            id = nextId,
            title = title.take(40),
            dueAtMillis = dueAtMillis,
            priority = priority
        )
    }

    // 尝试从中文表达中推断时间戳（仅处理常见格式）
    private fun inferDueMillisFromText(text: String, baseNow: Long = System.currentTimeMillis()): Long? {
        val tz = java.util.TimeZone.getDefault()
        val meridiemRegex = "(凌晨|早上|上午|中午|下午|晚上)?"
        val hourRegex = "(\\d{1,2})"
        val halfOrMinRegex = "(半)|(\\d{1,2})分?"
        val pattern = Regex("$meridiemRegex${hourRegex}点(?:$halfOrMinRegex)?")
        val m = pattern.find(text)
        if (m != null) {
            val meridiem = m.groups[1]?.value ?: ""
            var hour = m.groups[2]?.value?.toIntOrNull() ?: return null
            val isHalf = m.groups[3]?.value != null
            val minute = if (isHalf) 30 else m.groups[4]?.value?.toIntOrNull() ?: 0
            when (meridiem) {
                "下午", "晚上" -> if (hour < 12) hour += 12
                "中午" -> hour = 12
                // "凌晨"/"早上"/"上午" 保持原小时
            }
            val cal = java.util.Calendar.getInstance(tz)
            cal.timeInMillis = baseNow
            cal.set(java.util.Calendar.SECOND, 0)
            cal.set(java.util.Calendar.MILLISECOND, 0)
            cal.set(java.util.Calendar.HOUR_OF_DAY, hour)
            cal.set(java.util.Calendar.MINUTE, minute)
            // 如果目标时间已过去，则顺延到明天同一时间
            if (cal.timeInMillis <= baseNow) {
                cal.add(java.util.Calendar.DAY_OF_MONTH, 1)
            }
            return cal.timeInMillis
        }
        return null
    }
}